package com.css.fxfzmh.login.service.impl;

import com.css.fxfzmh.base.user.repository.CertificationRepository;
import com.css.fxfzmh.base.user.repository.UserRepository;
import com.css.fxfzmh.base.user.repository.entity.CertificationEntity;
import com.css.fxfzmh.base.user.repository.entity.UserEntity;
import com.css.fxfzmh.config.property.FxfzConfig;
import com.css.fxfzmh.constants.CertificationLockedEnum;
import com.css.fxfzmh.constants.LoginStatusCodeEnum;
import com.css.fxfzmh.constants.YNEnum;
import com.css.fxfzmh.login.security.SecretKeyRuleService;
import com.css.fxfzmh.login.service.LoginService;
import com.css.fxfzmh.login.viewobjects.LoginStatus;
import com.css.fxfzmh.login.viewobjects.PwdCheck;
import com.css.fxfzmh.util.PlatformDateUtils;
import com.css.fxfzmh.util.PlatformObjectUtils;
import com.css.fxfzmh.util.PlatformRSAUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import java.math.BigDecimal;
import java.util.Calendar;
import java.util.Date;

@Slf4j
@Service
public class LoginServiceImpl implements LoginService {
    @Autowired
    UserRepository userRepository;
    @Autowired
    CertificationRepository certificationRepository;
    @Autowired
    SecretKeyRuleService secretKeyRuleService;
    @Autowired
    FxfzConfig fxfzConfig;

    private static final long ONE_HOUR_MILLISECOND = 1000 * 60 * 60L;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public LoginStatus loginIn(String userCode, String cipher, String privateKey, String clientIp, HttpServletRequest request) throws Exception{
        LoginStatus status = new LoginStatus();
        PwdCheck check = checkPwd(userCode,cipher,privateKey);
        if(check.isSuccess()){
            status.setSuccess(true);
            status.setUserId(check.getUserId());
            status.setStatus(LoginStatusCodeEnum.SUCCESS.getCode());
            status.setLoginSuccessUrl(request.getContextPath()+"/platform/home");
        }else{
            if(LoginStatusCodeEnum.ACCOUNT_NOT_EXIST.getCode().equals(check.getErrorCode())){
                //用户不存在
                status.setStatus(LoginStatusCodeEnum.ACCOUNT_NOT_EXIST.getCode());
                status.setMessage(LoginStatusCodeEnum.ACCOUNT_NOT_EXIST.getMessage());
            }else if(LoginStatusCodeEnum.INACTIVE.getCode().equals(check.getErrorCode())){
                //用户未激活
                status.setStatus(LoginStatusCodeEnum.INACTIVE.getCode());
                status.setMessage(LoginStatusCodeEnum.INACTIVE.getMessage());
            }else if(LoginStatusCodeEnum.TOO_MANY_FAILURES.getCode().equals(check.getErrorCode())){
                //失败次数过多锁定
                status.setStatus(LoginStatusCodeEnum.TOO_MANY_FAILURES.getCode());
                status.setMessage(LoginStatusCodeEnum.TOO_MANY_FAILURES.getMessage());
                status.setRemainLockedTime(check.getRemainLockedTime());
            }else if(LoginStatusCodeEnum.PASSWORD_ERROR.getCode().equals(check.getErrorCode())){
                //用户名或密码错误
                status.setStatus(LoginStatusCodeEnum.PASSWORD_ERROR.getCode());
                status.setMessage(LoginStatusCodeEnum.PASSWORD_ERROR.getMessage());
                status.setRetryTimes(check.getRetryTimes());
            }else if(LoginStatusCodeEnum.ADMIN_LOCKED.getCode().equals(check.getErrorCode())){
                status.setStatus(LoginStatusCodeEnum.ADMIN_LOCKED.getCode());
                status.setMessage(LoginStatusCodeEnum.ADMIN_LOCKED.getMessage());
            }else if(LoginStatusCodeEnum.PWD_EXPIRED.getCode().equals(check.getErrorCode())){
                //密码过期
                status.setStatus((LoginStatusCodeEnum.PWD_EXPIRED.getCode()));
                status.setMessage(LoginStatusCodeEnum.PWD_EXPIRED.getMessage());
            }else if(LoginStatusCodeEnum.FORCE_CHANGE_PWD.getCode().equals(check.getErrorCode())){
                //强制修改默认密码
                status.setStatus((LoginStatusCodeEnum.FORCE_CHANGE_PWD.getCode()));
                status.setMessage(LoginStatusCodeEnum.FORCE_CHANGE_PWD.getMessage());
            }

            //loginFailLog(check.getUserId(),userCode,clientIp,status.getStatus());
        }
        return status;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public PwdCheck checkPwd(String userCode, String cipher, String privateKey){
        UserEntity userEntity = userRepository.findOneByUserCodeAndIsValid(userCode, YNEnum.Y.toString());
        PwdCheck check = new PwdCheck();
        if(PlatformObjectUtils.isEmpty(userEntity)){
            //用户不存在
            log.error(LoginStatusCodeEnum.ACCOUNT_NOT_EXIST.getMessage()+",userCode="+userCode);
            check.setSuccess(false);
            check.setErrorCode(LoginStatusCodeEnum.ACCOUNT_NOT_EXIST.getCode());
            check.setErrorMessage(LoginStatusCodeEnum.ACCOUNT_NOT_EXIST.getMessage());
            return check;
        }else{
            String userId = userEntity.getUserId();
            check.setUserId(userId);
            CertificationEntity certificationEntity = certificationRepository.findById(userEntity.getUserId());
            if(PlatformObjectUtils.isNotEmpty(certificationEntity)){
                //sysPwdConfigEntity = sysPwdConfigService.getSysPwdConfigEntity();
                check = checkLocked(check, certificationEntity);
                if (isShouldReturnCheck(check)) {
                    return check;
                }
                String realPwd = null;
                if (isNeedCheckPwd(check, certificationEntity.getLoginFailTime())) {
                    //解密
                    try {
                        realPwd = getRealPassword(privateKey, cipher);
                        System.out.println("密码为："+realPwd);
                    }catch (Exception e){
                        log.error(LoginStatusCodeEnum.PASSWORD_ERROR.getMessage()+",userId="+userEntity.getUserId());
                        check.setSuccess(false);
                        check.setErrorCode(LoginStatusCodeEnum.PASSWORD_ERROR.getCode());
                        check.setErrorMessage(LoginStatusCodeEnum.PASSWORD_ERROR.getMessage());
                        return check;
                    }
                    //超过锁定时间,才比较密码是否符合
                    boolean isMatch = secretKeyRuleService.check(certificationEntity, realPwd);
                    if (isMatch) {
                        checkPwdSuccess(check, userId, certificationEntity.getLoginFailNum() == null ? 0 : certificationEntity.getLoginFailNum());
                        //默认密码校验：0 允许 1 强制修改
                        if(YNEnum.Y.toString().equals(fxfzConfig.getIsModify())){
                            if(realPwd.equals(fxfzConfig.getDefaultPwd())){
                                check.setSuccess(false);
                                check.setErrorMessage(LoginStatusCodeEnum.FORCE_CHANGE_PWD.getMessage());
                                check.setErrorCode(LoginStatusCodeEnum.FORCE_CHANGE_PWD.getCode());
                                return check;
                            }
                        }
                        //密码过期校验
                        if(!PlatformObjectUtils.isEmpty(fxfzConfig.getPwdExpired())){
                            Calendar c = Calendar.getInstance();
                            c.setTime(certificationEntity.getPwdUpdateTime());
                            c.add(Calendar.DATE, fxfzConfig.getPwdExpired());
                            Date validDate = c.getTime();
                            if(validDate.before(PlatformDateUtils.getCurrentTimestamp())){
                                check.setSuccess(false);
                                check.setErrorCode(LoginStatusCodeEnum.PWD_EXPIRED.getCode());
                                check.setErrorMessage(LoginStatusCodeEnum.PWD_EXPIRED.getMessage());
                                return check;
                            }
                        }
                    } else {
                        checkPwdFailed(check, userId, certificationEntity.getLoginFailNum() == null ? 0 : certificationEntity.getLoginFailNum());
                    }
                } else {
                    //未超过锁定时间
                    String remainLockedTime = getRemainLockedTime(certificationEntity.getLoginFailTime());
                    check.setRemainLockedTime(remainLockedTime);
                }
            }else{
                log.error(LoginStatusCodeEnum.CERTIFICATION_NOT_EXIST.getMessage()+",userId="+userEntity.getUserId());
                check.setSuccess(false);
                check.setErrorCode(LoginStatusCodeEnum.CERTIFICATION_NOT_EXIST.getCode());
                check.setErrorMessage(LoginStatusCodeEnum.CERTIFICATION_NOT_EXIST.getMessage());
                return check;
            }
        }
        return check;
    }

    private PwdCheck checkLocked(PwdCheck check, CertificationEntity certificationEntity) {
        if(CertificationLockedEnum.INACTIVE.getCode().equals(certificationEntity.getLocked())){
            check.setSuccess(false);
            check.setErrorCode(LoginStatusCodeEnum.INACTIVE.getCode());
            check.setErrorMessage(LoginStatusCodeEnum.INACTIVE.getMessage());
            return check;
        }else if(CertificationLockedEnum.TOO_MANY_FAILURES.getCode().equals(certificationEntity.getLocked())){
            check.setSuccess(false);
            check.setErrorCode(LoginStatusCodeEnum.TOO_MANY_FAILURES.getCode());
            check.setErrorMessage(LoginStatusCodeEnum.TOO_MANY_FAILURES.getMessage());
            return check;
        }else if(CertificationLockedEnum.ADMIN_LOCKED.getCode().equals(certificationEntity.getLocked())){
            check.setSuccess(false);
            check.setErrorCode(LoginStatusCodeEnum.ADMIN_LOCKED.getCode());
            check.setErrorMessage(LoginStatusCodeEnum.ADMIN_LOCKED.getMessage());
        }else{
            check.setSuccess(true);
        }
        return check;
    }

    private boolean isShouldReturnCheck(PwdCheck check) {
        //失败并且原因是用户未激活或者管理员锁定，直接返回
        return !check.isSuccess() && (CertificationLockedEnum.INACTIVE.getCode().equals(check.getErrorCode()) ||
                CertificationLockedEnum.ADMIN_LOCKED.getCode().equals(check.getErrorCode()));
    }
    private boolean isNeedCheckPwd(PwdCheck check, Date loginFailTime) {
        //校验锁定状态成功，或者超过失败次数过多锁定时间
        return check.isSuccess() || (CertificationLockedEnum.TOO_MANY_FAILURES.getCode().equals(check.getErrorCode()) &&
                isOverLockedTime(loginFailTime));
    }

    private String getRealPassword(String privateKey, String RSAPassword) throws Exception {
        String realPwd = PlatformRSAUtils.decrypt(RSAPassword, privateKey);
        return realPwd;
    }

    private boolean isOverLockedTime(Date loginFailTime) {
        //登录次数过多锁定时间，小时数 (sysPwdConfigEntity.getLockedTime())
        return PlatformDateUtils.parseMillisecond(PlatformDateUtils.getCurrentTimestamp()) - PlatformDateUtils.parseMillisecond(loginFailTime) > (2 * ONE_HOUR_MILLISECOND);
    }

    private PwdCheck checkPwdSuccess(PwdCheck check, String userId, int loginFailNum) {
        check.setSuccess(true);
        if(CertificationLockedEnum.TOO_MANY_FAILURES.getCode().equals(check.getErrorCode())){
            //解锁，重置失败次数和时间
            certificationRepository.unlockLoginFail(userId,userId, PlatformDateUtils.getCurrentTimestamp());
        }else if(loginFailNum > 0){
            //成功后，重置失败次数，之前失败过才重置
            certificationRepository.resetLoginFail(userId,userId, PlatformDateUtils.getCurrentTimestamp());
        }
        return check;
    }

    private PwdCheck checkPwdFailed(PwdCheck check, String userId, int loginFailNum) {
        if(CertificationLockedEnum.TOO_MANY_FAILURES.getCode().equals(check.getErrorCode())){
            //超时后，验证又失败，更新失败时间
            certificationRepository.updateLoginFailTime(userId,userId, PlatformDateUtils.getCurrentTimestamp());
        }else{
            int tooManyFailures = fxfzConfig.getTooManyFailures();
            if((loginFailNum + 1) >= tooManyFailures){
                //锁定
                certificationRepository.loginFailLock(userId, PlatformDateUtils.getCurrentTimestamp(),userId, PlatformDateUtils.getCurrentTimestamp());
                check.setSuccess(false);
                check.setErrorCode(LoginStatusCodeEnum.TOO_MANY_FAILURES.getCode());
                check.setErrorMessage(LoginStatusCodeEnum.TOO_MANY_FAILURES.getMessage());
                check.setRemainLockedTime(getRemainLockedTime(PlatformDateUtils.getCurrentTimestamp()));
                return check;
            }else{//失败次数+1
                certificationRepository.addLoginFailNum(userId,userId, PlatformDateUtils.getCurrentTimestamp());
                check.setRetryTimes(tooManyFailures - loginFailNum - 1);
            }
        }
        check.setSuccess(false);
        check.setErrorCode(LoginStatusCodeEnum.PASSWORD_ERROR.getCode());
        check.setErrorMessage(LoginStatusCodeEnum.PASSWORD_ERROR.getMessage());
        return check;
    }

    private String getRemainLockedTime(Date loginFailTime) {
        //登录次数过多锁定时间，小时数
        int hours = fxfzConfig.getLockedTime();
        Long lockedTime = hours * ONE_HOUR_MILLISECOND - (PlatformDateUtils.parseMillisecond(PlatformDateUtils.getCurrentTimestamp()) - PlatformDateUtils.parseMillisecond(loginFailTime));
        if (lockedTime >= ONE_HOUR_MILLISECOND) {
            Long minute = lockedTime / 1000L / 60L / 60L;
            BigDecimal minuteDecimal =  new BigDecimal(minute).setScale(hours, BigDecimal.ROUND_HALF_UP);
            return minuteDecimal + "小时";
        } else {
            Double minute = (Math.ceil(lockedTime / 1000L / 60L));
            int minuteValue  = minute.intValue();
            return minuteValue + "分钟";
        }
    }
}
